package com.example.tongyao.system.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.tongyao.system.entity.SysConfig;
import com.example.tongyao.system.entity.SysErrorLog;
import com.example.tongyao.system.entity.SysLog;
import com.example.tongyao.system.entity.SysUser;
import com.example.tongyao.system.service.ISysConfigService;
import com.example.tongyao.system.service.ISysErrorLogService;
import com.example.tongyao.system.service.ISysLogService;
import com.example.tongyao.utils.DateUtils;
import com.example.tongyao.utils.StringUtils;
import com.example.tongyao.utils.token.UserUtils;
import eu.bitwalker.useragentutils.*;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.example.tongyao.utils.DateUtils.calculateHMS;

/**
 * <p>
 * 系统日志记录工具类
 * </p>
 *
 * @author tongyao
 * @since 2021-11-07
 */
@Log4j2
@Aspect
@Component
public class LogAspect {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private Long startTime;

    private RequestAttributes requestAttributes;

    private HttpServletRequest request;

    private Signature signature;

    private String sourceIp;

    private StringBuilder requestLog;

    private String[] paramNames;

    private Object[] paramValues;

    private String agent;

    private UserAgent userAgent;

    private Browser browser;

    private BrowserType browserType;

    private OperatingSystem operatingSystem;

    private DeviceType deviceType;

    private String systemType;

    private Object resultJson;

    private Long endTime;

    private String formatStartTime;

    private String formatendTime;

    long calculate;

    @Resource
    private ISysLogService iSysLogService;

    @Resource
    private ISysErrorLogService iSysErrorLogService;

    @Resource
    public ISysConfigService iSysConfigService;


    //定义切入点 就是需要拦截的切面
    /*@Pointcut("execution(public * com.example.tongyao.system.controller.*.*(..))")
    public void controllerMethod() {}*/

    /**
     * 进入方法请求执行前
     *
     * @param joinPoint
     * @throws Exception
     */
    /*@Before("controllerMethod()")
    public void LogRequestinfo(JoinPoint joinPoint) {
        //添加记录登录日志操作
    }*/


    /**
     * 进入方法请求执行后
     *
     * @param o
     * @throws Exception
     */
    /*@AfterReturning(returning = "o", pointcut = "controllerMethod()")
    public void logResultVOinfo(Object o) {
        //返回内容记录
    }*/


    /**
     * 返回通知 正常结束时进入此方法
     *
     * @param ret
     */
    /*@AfterReturning(returning = "ret", pointcut = "controllerMethod()")
    public void doAfterReturning(Object ret) {
        //正常结束时进入此方法
    }*/

    /**
     * 该切面发生异常信息时进行拦截
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(pointcut = "execution(* com.example.tongyao.system.controller.*.*(..)) or " +
            "execution(* com.example.tongyao.system.config.*.*(..)) or " +
            "execution(* com.example.tongyao.system.exception.*.*(..))", throwing = "ex")
    public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
        startTime = System.currentTimeMillis();
        //从配置里获取是否有权限开启日志记录
        SysConfig sysConfig = iSysConfigService.getConfigByKey("sys_error_log");
        if(StringUtils.isNull(sysConfig)){
            throw new RuntimeException("系统配置：${sys_error_log} 为空！");
        }

        if(sysConfig.getConfigValue().equals("true")){
            requestAttributes = RequestContextHolder.getRequestAttributes();
            request = ((ServletRequestAttributes) requestAttributes).getRequest();
            signature = joinPoint.getSignature();
            requestLog = new StringBuilder();
            paramNames = ((MethodSignature) signature).getParameterNames();
            paramValues = joinPoint.getArgs();
            int paramLength = null == paramNames ? 0 : paramNames.length;
            if (paramLength == 0) {
                requestLog.append("该接口没有参数。");
            } else {
                requestLog.append("[");
                for (int i = 0; i < paramLength - 1; i++) {
                    requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(filterObject(paramValues[i]))).append(",");
                }
                requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(filterObject(paramValues[paramLength - 1]))).append("]");
            }
            //来源IP
            sourceIp = request.getRemoteAddr();
            if ((request.getRemoteAddr()).equals("0:0:0:0:0:0:0:1")) {
                sourceIp = "127.0.0.1";
            }
            //user-agent
            agent = request.getHeader("User-Agent");
            userAgent = UserAgent.parseUserAgentString(agent);
            browser = userAgent.getBrowser();
            browserType = browser.getBrowserType();
            //获取操作系统对象
            operatingSystem = userAgent.getOperatingSystem();
            deviceType = operatingSystem.getDeviceType();
            systemType = agent.substring(agent.indexOf("(") + 1, agent.indexOf(")"));

            //请求方法
            String logMethod = request.getMethod();
            //接口地址
            String logInterface = request.getRequestURI();
            //接口完整地址
            String fullLogInterface = request.getRequestURL().toString();
            //请求参数
            String sendData = requestLog.toString();

            //异常原因
            String exceptionReason = ex.getMessage();

            //异常类型
            String exceptionType = ex.getClass().toString();

            //完整异常
            String fullException = ex.toString();

            //详细异常
            String detailedException = getDetailedException(ex);

            //相应类
            String classPack = joinPoint.getSignature().toString();
            //浏览器
            String browserStr = browser.getName() + " (" + userAgent.getBrowserVersion() + ")";
            //浏览器内核
            String browserCore = browser.getRenderingEngine().toString();
            //浏览器类型
            String browserTypeStr = browserType.getName()+ " (" + browser.getBrowserType() + ")";
            //操作系统
            String operatingSystemStr = operatingSystem.getName()+ " (" + systemType + ")";
            //系统类型
            String systemType = deviceType.getName();



            SysErrorLog sysErrorLog = new SysErrorLog();
            sysErrorLog.setLogMethod(logMethod);
            sysErrorLog.setLogInterface(logInterface);
            sysErrorLog.setFullLogInterface(fullLogInterface);
            sysErrorLog.setSourceIp(sourceIp);
            sysErrorLog.setSendData(sendData);
            sysErrorLog.setExceptionReason(exceptionReason);
            sysErrorLog.setExceptionType(exceptionType);
            sysErrorLog.setFullException(fullException);
            sysErrorLog.setDetailedException(detailedException);
            sysErrorLog.setClassPack(classPack);
            sysErrorLog.setBrowser(browserStr);
            sysErrorLog.setBrowserCore(browserCore);
            sysErrorLog.setBrowserType(browserTypeStr);
            sysErrorLog.setOperatingSystem(operatingSystemStr);
            sysErrorLog.setSystemType(systemType);
            sysErrorLog.setUserAgent(agent);
            if(!UserUtils.getUserInfo().equals("免登录接口无用户信息！")){
                SysUser user = (SysUser) UserUtils.getUserInfo();
                sysErrorLog.setCreateBy(user.getUserId());
            }
            sysErrorLog.setCreateTime(DateUtils.getDateTime());
            iSysErrorLogService.save(sysErrorLog);

            /*备注：
             * 考虑错误日志是否需要记录非空验证错误？
             * 如果访问某一个接口出错，错误日志记录一次。本类中的deAround方法，获取不到返回值，也会报错，导致一下记录两条重复错误日志。
             * 考虑记录日志访问后，没有登录的情况下是否还需要记录？
             * 有些错误日志没有记录，比如令牌为空！
             * */

            //输出
            log.debug("请求方法：{}\t接口地址：{}\t完整接口地址：{}\t来源IP:{}\t请求参数：{}\t异常原因：{}\t异常类型：{}\t完整异常：{}\t详细异常：{}\t相应类：{}\t浏览器：{}\t浏览器内核：{}\t浏览器类型：{}\t操作系统：{}\t系统类型：{}\tuser-agent：{}",
                    logMethod,logInterface,fullLogInterface,sourceIp,sendData,exceptionReason,exceptionType,fullException,detailedException,classPack,browserStr,browserCore,browserTypeStr,operatingSystemStr,systemType,agent);

        }

        //关闭异常日志记录


    }

    /**
     * 全部输出
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Around("execution(public * com.example.tongyao.system.controller.*.*(..)) or execution(public * com.example.tongyao.core.controller.*.*(..))")
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
        /*String path = request.getRequestURI();

        if(request.getRequestURI().indexOf(".htlm") != -1){
            return joinPoint.proceed();
        }*/

        startTime = System.currentTimeMillis();
        //从配置里获取是否有权限开启日志记录
        SysConfig sysConfig = iSysConfigService.getConfigByKey("sys_log");
        if(StringUtils.isNull(sysConfig)){
            throw new RuntimeException("系统配置：${sys_log} 为空！");
        }

        if(sysConfig.getConfigValue().equals("false")){
            return joinPoint.proceed();
        }

        requestAttributes = RequestContextHolder.getRequestAttributes();
        request = ((ServletRequestAttributes) requestAttributes).getRequest();
        signature = joinPoint.getSignature();
        requestLog = new StringBuilder();
        paramNames = ((MethodSignature) signature).getParameterNames();
        paramValues = joinPoint.getArgs();
        int paramLength = null == paramNames ? 0 : paramNames.length;
        if (paramLength == 0) {
            requestLog.append("该接口没有参数。");
        } else {
            requestLog.append("[");
            for (int i = 0; i < paramLength - 1; i++) {
                requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(filterObject(paramValues[i]))).append(",");
            }
            requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(filterObject(paramValues[paramLength - 1]))).append("]");
        }
        //来源IP
        sourceIp = request.getRemoteAddr();
        if ((request.getRemoteAddr()).equals("0:0:0:0:0:0:0:1")) {
            sourceIp = "127.0.0.1";
        }
        //user-agent
        agent = request.getHeader("User-Agent");
        userAgent = UserAgent.parseUserAgentString(agent);
        browser = userAgent.getBrowser();
        browserType = browser.getBrowserType();
        //获取操作系统对象
        operatingSystem = userAgent.getOperatingSystem();
        deviceType = operatingSystem.getDeviceType();
        systemType = agent.substring(agent.indexOf("(") + 1, agent.indexOf(")"));

        //在访问html页面的时候 返回为null，所以报错
        resultJson = joinPoint.proceed();

        endTime = System.currentTimeMillis();
        calculate = endTime - startTime;


        //请求方法
        String logMethod = request.getMethod();
        //接口地址
        String logInterface = request.getRequestURI();
        //接口完整地址
        String fullLogInterface = request.getRequestURL().toString();
        //请求参数
        String sendData = requestLog.toString();
        //返回结果
        String dataResult = JSON.toJSONString(resultJson);
        //相应类
        String classPack = joinPoint.getSignature().toString();
        //浏览器
        String browserStr = browser.getName() + " (" + userAgent.getBrowserVersion() + ")";
        //浏览器内核
        String browserCore = browser.getRenderingEngine().toString();
        //浏览器类型
        String browserTypeStr = browserType.getName()+ " (" + browser.getBrowserType() + ")";
        //操作系统
        String operatingSystemStr = operatingSystem.getName()+ " (" + systemType + ")";
        //系统类型
        String systemType = deviceType.getName();

        //开始时间
        formatStartTime = simpleDateFormat.format(startTime);
        //结束时间
        formatendTime = simpleDateFormat.format(endTime);
        //总耗时
        String elapsedTime = calculateHMS(calculate);

        SysUser user = (SysUser) UserUtils.getUserInfo();
        SysLog sysLog = new SysLog();
        sysLog.setLogMethod(logMethod);
        sysLog.setLogInterface(logInterface);
        sysLog.setFullLogInterface(fullLogInterface);
        sysLog.setSourceIp(sourceIp);
        sysLog.setSendData(sendData);
        sysLog.setDataResult(dataResult);
        sysLog.setClassPack(classPack);
        sysLog.setBrowser(browserStr);
        sysLog.setBrowserCore(browserCore);
        sysLog.setBrowserType(browserTypeStr);
        sysLog.setOperatingSystem(operatingSystemStr);
        sysLog.setSystemType(systemType);
        sysLog.setStartTime(new Date(startTime));
        sysLog.setEndTime(new Date(endTime));
        sysLog.setElapsedTime(elapsedTime);
        sysLog.setUserAgent(agent);
        sysLog.setCreateBy(user.getUserId());
        sysLog.setCreateTime(DateUtils.getDateTime());
        iSysLogService.save(sysLog);


        //输出
        log.debug("请求方法：{}\t接口地址：{}\t完整接口地址：{}\t来源IP:{}\t请求参数：{}\t返回结果：{}\t相应类：{}\t浏览器：{}\t浏览器内核：{}\t浏览器类型：{}\t操作系统：{}\t系统类型：{}\t开始时间：{}\t结束时间：{}\t总耗时：{}\tuser-agent：{}",
                logMethod,logInterface,fullLogInterface,sourceIp,sendData,dataResult,classPack,browserStr,browserCore,browserTypeStr,operatingSystemStr,systemType,formatStartTime,formatendTime,elapsedTime,agent);

        return resultJson;
    }

    /**
     * 解决 相应对象转换实例失败过滤
     * @param object
     * @return
     */
    private HttpServletRequest httpServletRequest;

    private Enumeration enu;

    private Map typeMap;

    private MultipartFile[] multipartFiles;

    public Object filterObject(Object object) {

        typeMap = new HashMap();
        if(object instanceof HttpServletRequest){
            httpServletRequest = (HttpServletRequest) object;
            enu = httpServletRequest.getParameterNames();
            while(enu.hasMoreElements()){
                String paramsKey = enu.nextElement().toString();
                String paramsValue = httpServletRequest.getParameter(paramsKey);
                typeMap.put(paramsKey,paramsValue);
            }
            object = typeMap;
        }

        if(object instanceof HttpServletResponse){
            object = "Response类型暂时不支持。";
        }

        if(object instanceof MultipartFile || object instanceof MultipartFile[]){
            multipartFiles = (MultipartFile[]) object;
            for (MultipartFile multipartFile : multipartFiles){
                typeMap.put("local_file",multipartFile.getOriginalFilename());
                typeMap.put("file_size",multipartFile.getSize());
                typeMap.put("file_type",multipartFile.getContentType());
            }
            object = typeMap;
        }
        return object;
    }

    /**
     * 获得详细异常信息
     * @param ex
     * @return
     */

    private StringWriter stringWriter;

    private PrintWriter writer;

    private StringBuffer buffer;

    public String getDetailedException(Exception ex) {
        stringWriter = new StringWriter();
        writer = new PrintWriter(stringWriter);
        ex.printStackTrace(writer);
        buffer = stringWriter.getBuffer();
        return buffer.toString();
    }

}